﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using DateTimeExtensions;

namespace DateTimeEnglishParser
{
    internal static class StringHelpers
    {
        public static string GetWord(this string sentance, int wordCount)
        {
            return sentance.Split()[wordCount - 1];
        }

        public static string GetStringAfterWord(this string sentance, int wordCount)
        {
            string returnString = String.Empty;
            string[] words = sentance.Split();
            for (int i = wordCount; i < words.Length; i++)
            {
                returnString += words[i] + " ";
            }
            return returnString.TrimEnd();
        }
    }
    public interface IDateTimeParserComponent
    {
        DateTime Parse(DateTime baseTime, out string dateString, out bool done);
    }
    public class DateTimeEnglishParser
    {
        private delegate DateTime ParseDelegate(DateTime baseTime, ref string dateString);

        private const string nextWeekIndicator = "next ";
        private const string lastWeekIndicator = "last ";
        private static readonly Regex unitsFromMatch = new Regex(@"(\w+) (week|day)(?:s|) (\w+) ");
        private static readonly string[] pastIndicators = new[] {"ago", "before"};
        private List<ParseDelegate> parseDelegates = new List<ParseDelegate>();

        public DateTimeEnglishParser()
        {
            parseDelegates.Add(ToLower);
            parseDelegates.Add(ConvertWordsToNumbers);
            parseDelegates.Add(ConvertPrecedingIn);
            parseDelegates.Add(ClarifyTrailingNext);
            parseDelegates.Add(ClarifyTrailingAgo);
            parseDelegates.Add(NextWeek);
            parseDelegates.Add(LastWeek);
            parseDelegates.Add(LookForUnitMultiplierPattern);
        }

        private static DateTime NextWeek(DateTime baseTime, ref string dateString)
        {
            if (dateString.StartsWith(nextWeekIndicator))
            {
                dateString = dateString.Substring(nextWeekIndicator.Length);
                baseTime += TimeSpan.FromDays(7);                
            }
            return baseTime;
        }

        private static DateTime LastWeek(DateTime baseTime, ref string dateString)
        {
            if (dateString.StartsWith(lastWeekIndicator))
            {
                dateString = dateString.Substring(lastWeekIndicator.Length);
                baseTime += TimeSpan.FromDays(-7);
            }
            return baseTime;
        }

        private static DateTime ToLower(DateTime baseTime, ref string dateString)
        {
            dateString = dateString.ToLowerInvariant();
            return baseTime;
        }

        private static DateTime LookForUnitMultiplierPattern(DateTime baseTime, ref string dateString)
        {
            Match weeksMatch = unitsFromMatch.Match(dateString);
            if (weeksMatch.Success)
            {
                int multiplier = Int32.Parse(weeksMatch.Groups[1].Value);
                baseTime +=
                    TimeSpan.FromDays(DaysFromUnits(weeksMatch.Groups[2].Value, multiplier, weeksMatch.Groups[3].Value));
                dateString = unitsFromMatch.Replace(dateString, "", 1);
            }
            return baseTime;            
        }

        public DateTime ParseRelative(DateTime baseTime, string dateString)
        {
            DateTime dateBase = baseTime.Midnight();
            foreach (ParseDelegate parseDelegate in parseDelegates)
            {
                dateBase = parseDelegate(dateBase, ref dateString);
            }


            //Match weeksMatch = unitsFromMatch.Match(dateString);
            //if (weeksMatch.Success)
            //{
            //    int multiplier = Int32.Parse(weeksMatch.Groups[1].Value);
            //    return
            //        this.ParseRelative(dateBase, unitsFromMatch.Replace(dateString, "", 1)).AddDays(
            //            DaysFromUnits(weeksMatch.Groups[2].Value, multiplier, weeksMatch.Groups[3].Value));
            //}

            if (StringIsDate(dateString))
            {
                return dateBase.Next(StringToDayOfWeek(dateString));
            }

            if (StringIsDate(dateString.GetWord(1)))
            {
                return
                    this.ParseRelative(dateBase, dateString.GetStringAfterWord(2)).Next(
                        StringToDayOfWeek(dateString.GetWord(1)));
            }

            switch (dateString)
            {
                case "tomorrow":
                    return dateBase.AddDays(1);
                case "yesterday":
                    return dateBase.AddDays(-1);
                case "today":
                    return dateBase;
                case "day after tomorrow":
                    return dateBase.AddDays(2);
            }
            return dateBase;
        }

        private static DateTime ClarifyTrailingAgo(DateTime timeBase, ref string dateString)
        {
            if (dateString.EndsWith("ago"))
            {
                dateString += " today";
            }
            return timeBase;
        }

        private static bool StringIsDate(string dateString)
        {
            return CultureInfo.CurrentCulture.DateTimeFormat.DayNames.Contains(dateString,
                                                                               StringComparer.CurrentCultureIgnoreCase);
        }

        private static DateTime ClarifyTrailingNext(DateTime timeBase, ref string dateString)
        {
            if (dateString.EndsWith("next"))
            {
                dateString += " " + dateString.GetWord(1);
            }
            return timeBase;
        }

        private static DayOfWeek StringToDayOfWeek(string dateString)
        {
            return (DayOfWeek) Enum.Parse(typeof (DayOfWeek), dateString, true);
        }

        private static DateTime ConvertPrecedingIn(DateTime baseTime, ref string dateString)
        {
            if (dateString.StartsWith("in "))
            {
                dateString += " from now";
            }
            return baseTime;
        }

        private static double DaysFromUnits(string unit, int multiplier, string direction)
        {
            int directionMultipler = pastIndicators.Contains(direction) ? -1 : 1;
            int dayUnits = unit == "day" ? 1 : 7;
            return dayUnits*multiplier * directionMultipler;
        }

        private static DateTime ConvertWordsToNumbers(DateTime baseTime, ref string dateString)
        {
            dateString = Regex.Replace(dateString, "one", "1");
            dateString = Regex.Replace(dateString, "the", "1");
            dateString = Regex.Replace(dateString, "two", "2");
            dateString = Regex.Replace(dateString, "three", "3");

            return baseTime;
        }
    }
}